﻿#include <learn/painter/painter2d.h>
#include <learn/utils/timer.h>

using namespace xi::painter;

Painter2D::Painter2D(int width, int height) : m_frame(width, height, TGA_Image::RGBA)
{
    // 启动计时
    duration();
}

long long Painter2D::duration() const
{
    static __clock::time_point start = __clock::now();
    __clock::time_point end = __clock::now();
    return std::chrono::duration_cast<__milliseconds>(end - start).count();
}

void Painter2D::refresh()
{
    for (int i = 0; i < m_frame->height; i++)
        for (int j = 0; j < m_frame->width; j++)
            m_frame.set(j, i, {});
}

void Painter2D::resize(int width, int height)
{
    m_frame.resize(width, height);
}

TGA_Image Painter2D::frame()
{
    return m_frame;
}

void Painter2D::set_frame(TGA_Image frame)
{
    m_frame = frame;
}

void Painter2D::draw_point(int x, int y, TGA_Color color, int size)
{
    if (size < 2)
        m_frame.set(x, y, color);
    else
    {
        draw_circle(x, y, size / 2, color);
        fill_boundary_4(x, y, color, color);
    }
}

void Painter2D::draw_line(int x0, int y0, int x1, int y1, TGA_Color color)
{
    // 判断哪个方向是主要延伸方向
    bool steep = false;
    if (std::abs(x0 - x1) < std::abs(y0 - y1))
    {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }

    // 确保主方向坐标递增
    if (x0 > x1)
    {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    int dx = x1 - x0;
    int dy = y1 - y0;

    // 利用 *2 规避中点比较时的 0.5 浮点数
    int derror2 = std::abs(dy) * 2;
    int error2 = 0;
    int y = y0;
    for (int x = x0; x <= x1; x++)
    {
        if (steep)
            m_frame.set(y, x, color);
        else
            m_frame.set(x, y, color);
        error2 += derror2;
        if (error2 > dx)
        {
            y += (y1 > y0 ? 1 : -1);
            error2 -= dx * 2;
        }
    }
}

void Painter2D::draw_line_antialias(int x0, int y0, int x1, int y1, TGA_Color color, int width)
{
    // 判断哪个方向是主要延伸方向
    bool steep = false;
    if (std::abs(x0 - x1) < std::abs(y0 - y1))
    {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }

    // 确保主方向坐标递增
    if (x0 > x1)
    {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    float dx = x1 - x0;
    float dy = y1 - y0;
    float gradient = (dx < FLT_MIN) ? 1.0f : dy / dx;
    float intersectY = y0;

    // 整数部分、小数部分、1-小数部分
    auto iPart = [](float x) -> int { return int(x); };
    auto fPart = [](float x) -> float { return x - int(x); };
    auto rPart = [](float x) -> float { return 1.0f - (x - int(x)); };

    // 混合颜色
    auto blend = [](TGA_Color color, float alpha) -> TGA_Color {
        return TGA_Color(color.bgra[2] * alpha, color.bgra[1] * alpha, color.bgra[0] * alpha, color.bgra[3]);
    };

    int w = width / 2;
    for (int x = x0; x <= x1; x++)
    {
        // 对于正数，取整函数会获得一个较小的值；对于负数，取整函数获得一个较大的值
        // 因此考虑当前像素位置和 -1 像素位置
        // 对于宽度超过 1 的情况，则将中间部分涂色，两端计算颜色放缩
        if (steep)
        {
            for (int i = 0; i < width; i++)
                m_frame.set(iPart(intersectY) - w + i, x, color);
            m_frame.set(iPart(intersectY) + w, x, blend(color, fPart(intersectY)));
            m_frame.set(iPart(intersectY) - w - 1, x, blend(color, rPart(intersectY)));
        }
        else
        {
            for (int i = 0; i < width; i++)
                m_frame.set(x, iPart(intersectY) - w + i, color);
            m_frame.set(x, iPart(intersectY) + w, blend(color, fPart(intersectY)));
            m_frame.set(x, iPart(intersectY) - w - 1, blend(color, rPart(intersectY)));
        }
        intersectY += gradient;
    }
}

void Painter2D::draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, TGA_Color color, int width)
{
    draw_line_antialias(x0, y0, x1, y1, color);
    draw_line_antialias(x1, y1, x2, y2, color);
    draw_line_antialias(x2, y2, x0, y0, color);
}

void Painter2D::draw_rectangle(int x0, int y0, int width, int height, TGA_Color color)
{
    for (int i = 0; i < width; i++)
    {
        m_frame.set(x0 + i, y0, color);
        m_frame.set(x0 + i, y0 + height - 1, color);
    }

    for (int i = 0; i < height; i++)
    {
        m_frame.set(x0, y0 + i, color);
        m_frame.set(x0 + width - 1, y0 + i, color);
    }
}

void Painter2D::draw_polyline(const std::vector<Vec2i> &points, TGA_Color color, int width)
{
    for (int i = 1; i < points.size(); i++)
        draw_line_antialias(points[i - 1].x(), points[i - 1].y(), points[i].x(), points[i].y(), color, width);
}

void Painter2D::draw_polygon(const std::vector<Vec2i> &points, TGA_Color color, int width)
{
    draw_line_antialias(points[0].x(), points[0].y(), points[points.size() - 1].x(), points[points.size() - 1].y(),
                        color, width);
    for (int i = 1; i < points.size(); i++)
        draw_line_antialias(points[i - 1].x(), points[i - 1].y(), points[i].x(), points[i].y(), color, width);
}

void Painter2D::draw_circle(int x, int y, int r, TGA_Color color)
{
    // 绘制圆上的点
    auto circlePoint = [&](int xc, int yc, int x, int y) {
        // 由于圆分成 8 个部分，利用对称性同时绘制 8 个点
        m_frame.set(xc + x, yc + y, color);
        m_frame.set(xc - x, yc + y, color);
        m_frame.set(xc + x, yc - y, color);
        m_frame.set(xc - x, yc - y, color);
        m_frame.set(xc + y, yc + x, color);
        m_frame.set(xc - y, yc + x, color);
        m_frame.set(xc + y, yc - x, color);
        m_frame.set(xc - y, yc - x, color);
    };

    // 距离判断条件
    int Dd = 2 * (1 - r);
    int dHD, dDV;
    int x0 = 0, y0 = r;

    // 下一个选择的点
    while (y0 >= x0)
    {
        // 绘制当前点
        circlePoint(x, y, x0, y0);

        // 根据 D 在圆内外分情况讨论，选择下一个点
        char nextPoint = 'D';
        if (Dd < 0)
        {
            // 选择 H 或 D，dHD = Dh + Dd
            dHD = 2 * (Dd + y0) - 1;
            nextPoint = (dHD <= 0) ? 'H' : 'D';
        }
        else if (Dd > 0)
        {
            // 选择 V 或 D，dDV = Dd + Dv
            dDV = 2 * (Dd - x0) - 1;
            nextPoint = (dDV <= 0) ? 'D' : 'V';
        }

        switch (nextPoint)
        {
        case 'H':
            // H = (x+1,y)
            x0++;
            Dd += 2 * x0 + 1;
            break;
        case 'D':
            // D = (x+1,y-1)
            x0++, y0--;
            Dd += 2 * (x0 - y0 + 1);
            break;
        case 'V':
            // V = (x,y-1)
            y0--;
            Dd += (-2 * y0 + 1);
            break;
        }
    }
}

void Painter2D::draw_circle_pointwise(int x, int y, int r, TGA_Color color)
{
    // 绘制圆上的点
    auto circlePoint = [&](int xc, int yc, int x, int y) {
        // 由于圆分成 8 个部分，利用对称性同时绘制 8 个点
        m_frame.set(xc + x, yc + y, color);
        m_frame.set(xc - x, yc + y, color);
        m_frame.set(xc + x, yc - y, color);
        m_frame.set(xc - x, yc - y, color);
        m_frame.set(xc + y, yc + x, color);
        m_frame.set(xc - y, yc + x, color);
        m_frame.set(xc + y, yc - x, color);
        m_frame.set(xc - y, yc - x, color);
    };

    int x0 = 0, y0 = r;
    int F = 0;

    // 绘制 1/8 圆
    while (y0 >= -x0)
    {
        circlePoint(x, y, x0, y0);
        if (F > 0)
        {
            F += -2 * y0 + 1;
            y0--;
        }
        else
        {
            F += -2 * x0 + 1;
            x0--;
        }
    }
}

void Painter2D::draw_circle_bresenham(int x, int y, int r, TGA_Color color)
{
    // 绘制圆上的点
    auto circlePoint = [&](int xc, int yc, int x, int y) {
        // 由于圆分成 8 个部分，利用对称性同时绘制 8 个点
        m_frame.set(xc + x, yc + y, color);
        m_frame.set(xc - x, yc + y, color);
        m_frame.set(xc + x, yc - y, color);
        m_frame.set(xc - x, yc - y, color);
        m_frame.set(xc + y, yc + x, color);
        m_frame.set(xc - y, yc + x, color);
        m_frame.set(xc + y, yc - x, color);
        m_frame.set(xc - y, yc - x, color);
    };

    // 从 (0,r) 位置出发
    int x0 = 0, y0 = r;
    int d = 3 - 2 * r;
    circlePoint(x, y, x0, y0);

    // 只绘制 1/8 圆
    while (y0 >= x0)
    {
        x0++;

        if (d > 0)
        {
            y0--;
            d = d + 4 * (x0 - y0) + 10;
        }
        else
            d = d + 4 * x0 + 6;
        circlePoint(x, y, x0, y0);
    }
}

void Painter2D::draw_ellispe(int x, int y, int width, int height, TGA_Color color)
{
    // 绘制椭圆上的点
    auto ellipsePoint = [&](int xc, int yc, int x, int y) {
        // 由于椭圆分成 4 个部分，利用对称性同时绘制 4 个点
        m_frame.set(xc + x, yc + y, color);
        m_frame.set(xc - x, yc + y, color);
        m_frame.set(xc + x, yc - y, color);
        m_frame.set(xc - x, yc - y, color);
    };

    // 椭圆两个半长轴
    float rx = width / 2.0;
    float ry = height / 2.0;

    // 判断哪个方向是主要延伸方向
    bool steep = false;
    if (rx < ry)
    {
        std::swap(x, y);
        std::swap(rx, ry);
        steep = true;
    }

    // 绘制直到斜率为 -1 的圆弧
    auto arc = [&](int x, int y, float rx, float ry) {
        int xc = x + rx;
        int yc = y + ry;
        int x0 = 0, y0 = ry;

        // 初始判断条件 (1, ry + 0.5)
        float test = ry * ry + rx * rx * ry + 0.25 * rx * rx;

        while (y0 >= 0)
        {
            // 绘制当前点共对称的四个点
            if (steep)
                ellipsePoint(yc, xc, y0, x0);
            else
                ellipsePoint(xc, yc, x0, y0);

            // 判断条件
            if (test < 0)
            {
                // 增量计算下一步
                test += ry * ry * (1 + 2.0 * x0);

                // 移动到右边一点或右下点
                x0 += 1;
            }
            else
            {
                // 增量计算下一步
                test += ry * ry * (1 + 2.0 * x0) + rx * rx * (1 - 2.0 * y0);

                // 移动到右下角点
                x0 += 1;
                y0 -= 1;
            }

            // 当斜率小于 -1 时退出
            if (ry * ry * x0 > rx * rx * y0)
                break;
        }
    };

    // 沿着长轴方向绘制，直到斜率小于 -1
    arc(x, y, rx, ry);

    // 交换 x,y
    std::swap(x, y);
    std::swap(rx, ry);
    steep = !steep;

    // 绘制剩余部分
    arc(x, y, rx, ry);
}

void Painter2D::draw_function(std::function<double(double)> f, int x0, int x1, int N, TGA_Color color, int width)
{
    double d = double(x1 - x0) / N;
    for (int i = 1; i < N + 1; i++)
        draw_line_antialias(x0 + (i - 1) * d, f(x0 + (i - 1) * d), x0 + i * d, f(x0 + i * d), color, width);
}

void Painter2D::fill_triangle(int x0, int y0, int x1, int y1, int x2, int y2, TGA_Color color)
{
    // 计算包围盒
    Vec2i rx(std::min(x0, std::min(x1, x2)), std::max(x0, std::max(x1, x2)));
    Vec2i ry(std::min(y0, std::min(y1, y2)), std::max(y0, std::max(y1, y2)));

    std::vector<Vec2f> pts({Vec2f{float(x0), float(y0)}, Vec2f{float(x1), float(y1)}, Vec2f{float(x2), float(y2)}});
    for (int x = rx[0]; x <= rx[1]; x++)
    {
        for (int y = ry[0]; y <= ry[1]; y++)
        {
            auto option = barycentric(Vec2f(float(x), float(y)), pts.data());
            if (option.has_value())
            {
                // 如果重心坐标有一个负值，则它在外侧
                auto coords = option.value();
                if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0)
                    continue;
                m_frame.set(x, y, color);
            }
        }
    }
}

void Painter2D::fill_rectangle(int x0, int y0, int width, int height, TGA_Color color)
{
    for (int i = 0; i < width; i++)
        for (int j = 0; j < height; j++)
            m_frame.set(x0 + i, y0 + j, color);
}

void Painter2D::fill_boundary_4(int x, int y, TGA_Color fillColor, TGA_Color boundaryColor)
{
    // 此函数是递归函数，不能计时
    if (x < 0 || x >= m_frame->width || y < 0 || y >= m_frame->height)
        return;

    if (m_frame.get(x, y) != boundaryColor && m_frame.get(x, y) != fillColor)
    {
        m_frame.set(x, y, fillColor);
        fill_boundary_4(x + 1, y, fillColor, boundaryColor);
        fill_boundary_4(x, y + 1, fillColor, boundaryColor);
        fill_boundary_4(x - 1, y, fillColor, boundaryColor);
        fill_boundary_4(x, y - 1, fillColor, boundaryColor);
    }
}

void Painter2D::fill_flood_4(int x, int y, TGA_Color fillColor, TGA_Color sameColor)
{
    // 此函数是递归函数，不能计时
    if (x < 0 || x >= m_frame->width || y < 0 || y >= m_frame->height)
        return;

    if (m_frame.get(x, y) == sameColor)
    {
        m_frame.set(x, y, fillColor);
        fill_flood_4(x + 1, y, fillColor, sameColor);
        fill_flood_4(x, y + 1, fillColor, sameColor);
        fill_flood_4(x - 1, y, fillColor, sameColor);
        fill_flood_4(x, y - 1, fillColor, sameColor);
    }
}

void Painter2D::draw_tga_image(int x, int y, const char *filename)
{
    TGA_Image image;
    image.read_tga_file(filename);

    int sx = x, sy = y;
    for (int i = 0; i < image->width; i++)
        for (int j = 0; j < image->height; j++)
            m_frame.set(i + sx, j + sy, image.get(i, j));
}
